/*
Uninclude: Tool for detecting unused #included files in C and C++ code.
    Copyright (C) 2012  Daniel Gutson, FuDePAN

    Uninclude is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    Uninclude is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with Uninclude.  If not, see <http://www.gnu.org/licenses/>.

    This is the includes tree node.
*/

#ifndef FILE_NODE_H
#define FILE_NODE_H

#include <list>
#include <string>
#include "uninclude/node_api.h"
#include "uninclude/tmp_file.h"
using namespace std;

class FileNode
{
    struct ChildInfo
    {
        size_t      line;
        FileNode*   child;

        ChildInfo() : child(NULL) {}

        ~ChildInfo()
        {
            delete child;
        }
    };

    const string fileName;
    CppTmpFile tmpFile;
    list<ChildInfo> children;

    // actually, this functions removes the line, rather than comment it,
    // because ther could be a comment after the include
    void commentLine(size_t lineNumber)
    {
        tmpFile.replaceLine(fileName, lineNumber, "");
    }

    void replaceInclude(size_t lineNumber, const string& newHeader)
    {
        tmpFile.replaceLine(fileName, lineNumber, "#include \"" + newHeader + "\"");
    }

protected:
    bool doStandAloneCheck;     // this is for header files only:
    FileNode(const string& fileName) : fileName(fileName), doStandAloneCheck(true) {}

    bool isLeaf() const
    {
        return children.empty();
    }

    const string& createAndGetTmpFileName()
    {
        tmpFile.create(fileName);
        return tmpFile.getName();
    }

    void commentIncludes(NodeAPI* api, const string& baseDir)
    {
        bool compilesOk = true;

        if (doStandAloneCheck)
        {
            compilesOk = api->checkIfIncludeFileCompiles(fileName);
            if (!compilesOk)
                api->reportIncludeNotStandAlone(fileName);
        }

        for( list<ChildInfo>::const_iterator it = children.begin(); it != children.end(); ++it )
        {
            commentLine(it->line);

            if (api->programsIdentical())
            {
                bool includeNeeded = false;
                // now ensure that, despite the include can be commented out in terms of the whole program,
                // commenting it will preserve the file stand-alone-ness (only for header files)
                if (doStandAloneCheck && compilesOk)
                {
                    // if when commented the line, this file doesn't compile, it means that the
                    // include is needed:
                    includeNeeded = !(api->checkIfIncludeFileCompiles(getTmpName()));
                }

                if (!includeNeeded)
                    api->reportIncludeNotNeeded(fileName, makeRelative(it->child->getName(), baseDir), it->line);
            }
            else if ( !it->child->isLeaf() )
            {
                replaceInclude(it->line, makeRelative(it->child->createAndGetTmpFileName(), baseDir));
                it->child->commentIncludes(api, baseDir);
            }
        }
    }

    const string& getName() const
    {
        return fileName;
    }

    string getTmpName() const
    {
        return tmpFile.getName();
    }
public:
    FileNode* addChild(const string& childName, size_t line)
    {
        children.push_back(ChildInfo());
        children.back().line = line;
        return children.back().child = new FileNode(childName);
    }
};

class RootNode : public FileNode
{
public:
    RootNode(const string& fileName) : FileNode(fileName)
    {
        createAndGetTmpFileName();  // just to create the TMP file.
        doStandAloneCheck = false;  // override
    }

    void commentIncludes(NodeAPI* api)
    {
        if ( !isLeaf() )
        {
            FileNode::commentIncludes(api, getBaseDir(getName()));
        }
    }

    using FileNode::getName;
    using FileNode::getTmpName;
};

#endif

